﻿using CalendarWindowsApp.Tools;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Appointments;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;

namespace CalendarWindowsApp.Controls.Calendar
{
    public sealed class BaseCalendar : Control
    {
        public static readonly DependencyProperty SelectedDateProperty =
           DependencyProperty.Register("SelectedDate",
           typeof(DateTime),
           typeof(BaseCalendar),
           new PropertyMetadata(DateTime.Today));

       
        private TextBlock calendarTitle;
        private List<Day> currentMonthCollection;       
        private Button forwardbutton;
        private List<Day> items;
        private List<Appointment> appointments= new List<Appointment>();
        private PanoramaPanel panoramaPanel;
        private Button previousButton;
        private CollectionViewSource dayCollectionViewSource;
        private CollectionViewSource dayHeaderCollectionViewSource;
        private CollectionViewSource weekCollectionViewSource;
        private CollectionViewSource weekHeaderCollectionViewSource;
        private CollectionViewSource monthCollectionViewSource;
        private SemanticZoom semanticZoom;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseCalendar"/> class.
        /// </summary>
        public BaseCalendar()
        {
            this.DefaultStyleKey = typeof(BaseCalendar);

           
        }

        /// <summary>
        /// Gets or sets the selected date.
        /// </summary>
        /// <value>
        /// The selected date.
        /// </value>
        public DateTime SelectedDate
        {
            get
            {
                return (DateTime)GetValue(SelectedDateProperty);
            }
            set
            {
                SetValue(SelectedDateProperty, value);
                // if the date is out of the span of the days list, reload the dates again
                // according to the new value.
                if (value.Date.CompareTo(this.items.Last().Date.AddMonths(-1)) > 0 ||
                    value.Date.CompareTo(this.items.First().Date.AddMonths(1)) < 1)
                    ReloadDates(value);

                this.RefreshCalendar(this.panoramaPanel.CenteredIndex);
            }
        }
     
        /// <summary>
        /// Set the IsSelected property to the day to false when the day is not selected. 
        /// </summary>
        internal void RefreshSelectedDay()
        {
            // TODO: Rework!
            if (panoramaPanel.CenteredIndex == 0)
                if (this.currentMonthCollection.Any(day => day.IsSelected &&
                    day != (this.monthCollectionViewSource.View.CurrentItem as Day)))
                    currentMonthCollection.First(day => day.IsSelected).IsSelected = false;
        }

        /// <summary>
        /// Invoked whenever application code or internal processes (such as a rebuilding layout pass) call ApplyTemplate.
        /// In simplest terms, this means the method is called just before a UI element displays in your app.
        /// Override this method to influence the default post-template logic of a class.
        /// </summary>
        protected override void OnApplyTemplate()
        {
            this.monthCollectionViewSource = (CollectionViewSource)GetTemplateChild("CollectionViewSourceMonth");
            this.weekCollectionViewSource = (CollectionViewSource)GetTemplateChild("CollectionViewSourceWeek");
            this.weekHeaderCollectionViewSource = (CollectionViewSource)GetTemplateChild("CollectionViewSourceWeekHeader");
            this.dayCollectionViewSource = (CollectionViewSource)GetTemplateChild("CollectionViewSourceDay");
            this.dayHeaderCollectionViewSource = (CollectionViewSource)GetTemplateChild("CollectionViewSourceDayHeader");

            this.semanticZoom = (SemanticZoom)GetTemplateChild("semanticZoom");
         
            this.previousButton = (Button)GetTemplateChild("previousButton");
            this.forwardbutton = (Button)GetTemplateChild("forwardButton");
            this.panoramaPanel = (PanoramaPanel)GetTemplateChild("PanoramaPanel");
            this.calendarTitle = (TextBlock)GetTemplateChild("titleTextBlock");

           
            this.panoramaPanel.IndexChanged += (index) =>
            {
                this.RefreshCalendar(index);
            };

            this.previousButton.Click += OnNavigatePrevious;
            this.forwardbutton.Click += OnNavigateForward;

            this.ReloadDates(DateTime.Today);
            this.ReloadFlyout(DateTime.Today);

            // Retrieve the settigns 
            var localSettings = ApplicationData.Current.LocalSettings;
            if (localSettings.Containers.ContainsKey("SettingsContainer"))
            {
                var settingIndex = (int)localSettings.Containers["SettingsContainer"].Values["DefaultView"];
                this.panoramaPanel.CenteredIndex = settingIndex;
            }

            base.OnApplyTemplate();
        }

        /// <summary>
        /// Called when the calendar navigate to a next date
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        private void OnNavigateForward(object sender, RoutedEventArgs e)
        {
            switch (this.panoramaPanel.CenteredIndex)
            {
                case 0:
                default:
                    this.SelectedDate = this.SelectedDate.AddMonths(1);
                    return;

                case 1:
                    this.SelectedDate = this.SelectedDate.AddDays(7);
                    return;

                case 2:
                    this.SelectedDate = this.SelectedDate.AddDays(1);
                    return;
            }
        }

        /// <summary>
        /// Called when the calendar navigate to the previous date
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        private void OnNavigatePrevious(object sender, RoutedEventArgs e)
        {
            switch (this.panoramaPanel.CenteredIndex)
            {
                case 0:
                default:
                    this.SelectedDate = this.SelectedDate.AddMonths(-1);
                    return;

                case 1:
                    this.SelectedDate = this.SelectedDate.AddDays(-7);
                    return;

                case 2:
                    this.SelectedDate = this.SelectedDate.AddDays(-1);
                    return;
            }
        }

        /// <summary>
        /// Refreshes the dates in the calendar according to the selected calendar type
        /// </summary>
        /// <param name="index">The index.</param>
        private void RefreshCalendar(int index)
        {
            switch (index)
            {
                case 0:
                default:
                    this.RefreshMonth();
                    break;

                case 1:
                    this.RefreshWeek();
                    break;

                case 2:
                    this.RefreshDay();
                    break;
            }
        }

        /// <summary>
        /// Retrieves the dates for the day view in the calendar
        /// </summary>
        private void RefreshDay()
        {
            this.calendarTitle.Text = this.SelectedDate.Date == DateTime.Today ?
                "Today" :
                this.SelectedDate.Date == DateTime.Today.AddDays(1) ?
                "Tomorrow" :
                this.SelectedDate.Date == DateTime.Today.AddDays(-1) ?
                "Yesterday" :
                this.SelectedDate.ToString("dddd");

            var startIndex = this.items.FindIndex(item => item.Date.Date == this.SelectedDate.Date);
            var dayViewCollection = this.items.GetRange(startIndex, 2);
            dayViewCollection.ForEach(day =>
            {
                day.Title = string.Format("{0}, {1}", day.Date.ToString("ddd"), day.Date.ToString("dd MMMM yy"));
            });

            this.dayCollectionViewSource.Source = dayViewCollection.GroupBy(item => item.Title);
            this.dayHeaderCollectionViewSource.Source = dayViewCollection.Select(item => item.Title);
        }

        /// <summary>
        /// Retrieves the dates for the month view in the calendar.
        /// </summary>
        private void RefreshMonth()
        {
            // Set the calendarTitle
            this.calendarTitle.Text = this.SelectedDate.ToString("MMMM yyyy");

            // Get the first day of the month
            var firstOfMonth = new DateTime(this.SelectedDate.Year,
                this.SelectedDate.Month,
                01);
            var lastOfMonth = (firstOfMonth.AddMonths(1)).AddDays(-1);

            var monthBegin = this.items.Where(day => day.Date == Enumerable.Range(0, 7)
                  .Select(i => firstOfMonth.AddDays(i * -1))
                  .FirstOrDefault(item => item.DayOfWeek == DayOfWeek.Monday))
              .FirstOrDefault();
            var monthEnd = this.items.Where(day => day.Date == Enumerable.Range(0, 7)
                  .Select(i => lastOfMonth.AddDays(i * 1))
                  .FirstOrDefault(item => item.DayOfWeek == DayOfWeek.Sunday))
                  .FirstOrDefault();
            var firstIndex = this.items.FindIndex(item => item == monthBegin);
            var lastIndex = this.items.FindIndex(item => item == monthEnd);

            // Get the month range
            this.currentMonthCollection = this.items.GetRange(firstIndex, lastIndex + 1 - firstIndex);
            this.currentMonthCollection.ForEach(item => item.Title = item.Date.ToString("ddd"));

            // Get the collection
            if (this.monthCollectionViewSource != null)
                this.monthCollectionViewSource.Source = 
                    currentMonthCollection.GroupBy(item => item.Date.DayOfWeek);
        }

        /// <summary>
        /// Retrieves the dates for the week view in the calendar
        /// </summary>
        private void RefreshWeek()
        {
            var startPoint = this.items.FindIndex(item => item.Date.Date == this.SelectedDate.Date);
            var startIndex = this.items[startPoint].Date.DayOfWeek ==
                DayOfWeek.Sunday ?
                startPoint :
                this.items.FindIndex(startPoint - 6, item => item.Date.DayOfWeek == DayOfWeek.Sunday);

            var weekCollection = this.items.GetRange(startIndex + 1, 7);
            weekCollection.ForEach(day =>
            {
                //day.Title =
                //    day.IsWeekEnd ?
                //    day.Date.ToString("ddd") :
                //    string.Format("{0}, {1}", day.Date.ToString("ddd"), day.Date.ToString("dd MMM yy"));
                day.Title =
                  string.Format("{0}, {1}", day.Date.ToString("ddd"), day.Date.ToString("MMM dd"));
            });

            // Set the Calendar Title
            // Get the Calendar Week number and compare it to the selected date.
            var thisWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                DateTime.Today,
                CalendarWeekRule.FirstFourDayWeek,
                DayOfWeek.Monday);
            var lastWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                DateTime.Today.AddDays(-7),
                CalendarWeekRule.FirstFourDayWeek,
                DayOfWeek.Monday);
            var nextWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                DateTime.Today.AddDays(7),
                CalendarWeekRule.FirstFourDayWeek,
                DayOfWeek.Monday);

            var selectedWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                SelectedDate,
                CalendarWeekRule.FirstFourDayWeek,
                DayOfWeek.Monday);

            this.calendarTitle.Text = selectedWeek == thisWeek ?
                "This Week" :
                selectedWeek == lastWeek ?
                "Last Week" :
                selectedWeek == nextWeek ?
                "Next Week" :
                weekCollection.First().Date.Month != weekCollection.Last().Date.Month ?
                string.Format("{0} - {1}", weekCollection.First().Date.ToString("MMM yyyy"), weekCollection.Last().Date.ToString("MMM yyyy")) :
                weekCollection.First().Date.ToString("MMM yyyy");

            // Get the week collection
            var test = weekCollection.GroupByMany(item => item.IsWeekEnd, item => item.Date);
            this.weekCollectionViewSource.Source = weekCollection.
                SelectMany(item => item.Items).GroupBy(item => item.hour);
                

            // Get the week header collection
            this.weekHeaderCollectionViewSource.Source = weekCollection.Select(item => item);
        }

        /// <summary>
        /// Lazy loads new dates into the common date list where the calendar items are loaded from.
        /// </summary>
        /// <param name="startingDate">The starting date.</param>
        private void ReloadDates(DateTime startingDate)
        {
            var start = startingDate.AddMonths(-3);
            var end = startingDate.AddMonths(9);
            var datesCollection = Enumerable.Range(0, end.Subtract(start).Days).Select(d => start.AddDays(d));
            this.items = datesCollection.Select(date => new Day(date, this)).ToList();
            RefreshCalendar(this.panoramaPanel.CenteredIndex);
        }

        private void ReloadFlyout(DateTime dateTime)
        {
            var start = new DateTime(dateTime.Year, 1, 1);
            var end = new DateTime(dateTime.Year, 12, 31);
            var source = Enumerable.Range(0, end.Subtract(start).Days).
                            Select(d => start.AddDays(d)).
                            GroupByMany(item=> item.Month, item=> item.DayOfWeek);
            
            // Set the zoomout Collection
            //(semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = source;
            //semanticZoom.ViewChangeStarted += (s, e) =>
            //{
            //    if (e.IsSourceZoomedInView == false)
            //        e.DestinationItem.Item = e.SourceItem.Item;
            //};
            
            
        }

      
    }
}